//模拟实现Axios
const defaults = {
    timeout : 0
}

//拦截器管理class
class Interceptors{
    constructor() {
        this.handles = [];
    }

    //设置拦截器
    use(fulfilled,rejected){
        this.handles.push({fulfilled,rejected});
    }

    //删除指定拦截器
    eject(id){
        this.handles[id] = null;
    }

    //清除所有拦截器
    clear(){
        this.handles = [];
    }

}

class Axios{
    constructor(defaultConfig={}) {
        this.defaults = defaultConfig;
        this.interceptors = {
            request : new Interceptors(),
            response : new Interceptors()
        }
    }
    request(configOrUrl={}, config={}){
        //如果传递的第一个参数为字符串, 则第二个参数为配置对象
        if(typeof configOrUrl === 'string'){
            //则将第一个参数的地址 , 保存到配置对象中
            config.url = configOrUrl;
        }else config = configOrUrl;
        //如果第一个参数不是字符串 , 那么第一个参数则为配置对象
        //那么将config对象直接设置为第一个参数即可


        //合并配置
        config = {
            ...this.defaults,
            ...config
        }
        //设置请求方式
        config.method = (config.method || 'get').toLowerCase();

        const {headers={}} = config;
        //拼接baseURL
        if(config.baseURL && !config.url.startsWith('http://')){
            config.url = config.baseURL + config.url;
        }
        //拼接params
        if(config.params){
            config.url = config.url + '?' + Object.keys(config.params).map(key=>key +'='+ config.params[key]).join('&');
        }

        //发送ajax请求
        const _dispatchRequest = function (config){
            //返回promise对象 , 其中包含信息
            return new Promise(function (resolve, reject){

                const xhr = new XMLHttpRequest();
                //指定响应体格式
                xhr.responseType = 'json';
                xhr.open(config.method, config.url);

                //判断配置项中是否存在cancelToken
                if(config.cancelToken){
                    //存在取消token , 当取消被调用的时候 ,
                    // cancel这个promise实例的状态会修改为成功
                    //那么就会触发成功回调  , 成功回调中取消了请求
                    config.cancelToken.cancel.then((value)=>{
                        //取消请求
                        xhr.abort();
                    })
                }
                //设置请求头
                for(let key in config.headers){
                    xhr.setRequestHeader(key, headers[key]);
                }

                // 当config.data的类型是对象：Content-Type:application/json
                // 当config.data的类型是字符串：Content-Type:application/x-www-form-urlencoded

                if(config.data){
                    if(typeof config.data === 'object'){
                        xhr.setRequestHeader('Content-Type', 'application/json');
                        config.data = JSON.stringify(config.data);
                    }else {
                        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                    }
                }
                xhr.send(config.data);//设置请求体
                xhr.onload = function (){
                    resolve({
                        config,
                        data : xhr.response,
                        headers : xhr.getAllResponseHeaders(),//获取响应头
                        request : xhr,
                        status : xhr.status,
                        statusText : xhr.statusText,
                    })
                }
                //处理超时情况  : 当请求的时长超过设置的timeout的时候会触发
                xhr.ontimeout = function (){
                    reject({
                        code:"ECONNABORTED",
                        config,
                        message:"timeout of "+config.timeout+"ms exceeded",
                        name:"AxiosError",
                        request : xhr
                    })
                }
                //处理异常情况
                xhr.onerror = function (){
                    reject({
                        code:"ERR_NETWORK",
                        config,
                        message:"timeout of "+config.timeout+"ms exceeded",
                        name:"AxiosError",
                        request : xhr
                    })
                }

                //处理取消事件
                xhr.onabort = function (){
                    reject({
                        code:"ERR_CANCELED",
                        message:"canceled",
                        name:"CanceledError"
                    })
                }

            })
        }

        const chain = [_dispatchRequest,undefined];
        this.interceptors.request.handles.forEach(item=>{
            if(item) return;//如果拦截器被清除为nll 则不需要添加到执行数组中
            chain.unshift(item.fulfilled,item.rejected);
        })
        this.interceptors.response.handles.forEach(item=>{
            if(item) return;
            chain.push(item.fulfilled,item.rejected)
        })
        let promise = Promise.resolve(config);
        while (chain.length){
            promise = promise.then(chain.shift(),chain.shift())
        }

        return promise
    }

    get(url,config){
        return this({
            url,
            ...config
        })
    }

    post(url,data,config){
        return this({
            method : 'post',
            url,
            data,
            ...config
        })
    }
}

function createInstance(defaultConfig){
    const context = new Axios(defaultConfig);
    const instance = Axios.prototype.request.bind(context);
    //instance就是request函数
    for (let key in context){
        //将context的属性复制到instance中
        instance[key] = context[key]
    }

    //将Axios原型中方法复制到instance实例中 , 也就是复制到axios中
    Object.getOwnPropertyNames(Axios.prototype).forEach(key=>{
        instance[key] = Axios.prototype[key];
    })
    return instance;
}
const axios = createInstance(defaults);
//axios就是instance实例 , instance就是request函数
//所以axios就是request函数


axios.CancelToken = function (callback){
    this.cancel = new Promise((resolve, reject)=>{
        callback(resolve);
    })
}

axios.isCancel = function (info){
    //通过判断响应信息 , 来判断是否因为取消请求而出现异常
    return info.code === "ERR_CANCELED";
}

axios.all = function (arr){
    return Promise.all(arr);
}

axios.spread = function (callback){
    //axios.spread作为then中的回调函数 ,
    //那么调用之后需要返回一个函数作为处理成功回调
    //使用apply 将数组拆分成一个一个参数传递到spread的回调函数中 , 实现了结果的拆分
    return function (value){
        callback.apply(null,value)
    }
}

export default axios;

//axios是一个Ajax请求库